package com.hqd.ch03.v45.core.annotation;

import com.hqd.ch03.utils.ClassUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;

final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnnotation<A> {

    private static final Map<Class<?>, Object> EMPTY_ARRAYS;

    static {
        Map<Class<?>, Object> emptyArrays = new HashMap<>();
        emptyArrays.put(boolean.class, new boolean[0]);
        emptyArrays.put(byte.class, new byte[0]);
        emptyArrays.put(char.class, new char[0]);
        emptyArrays.put(double.class, new double[0]);
        emptyArrays.put(float.class, new float[0]);
        emptyArrays.put(int.class, new int[0]);
        emptyArrays.put(long.class, new long[0]);
        emptyArrays.put(short.class, new short[0]);
        emptyArrays.put(String.class, new String[0]);
        EMPTY_ARRAYS = Collections.unmodifiableMap(emptyArrays);
    }

    /**
     * 组合注解的层级关系
     */
    private final AnnotationTypeMapping mapping;
    /**
     * 类加载器
     */
    private final ClassLoader classLoader;
    /**
     * 上级节点
     */
    private final Object source;
    /**
     * root节点
     */
    private final Object rootAttributes;

    /**
     * 获取注解
     */
    private final ValueExtractor valueExtractor;
    /**
     * 注解总数
     */
    private final int aggregateIndex;

    private final boolean useMergedValues;

    private final Predicate<String> attributeFilter;

    private final int[] resolvedRootMirrors;

    private final int[] resolvedMirrors;


    private TypeMappedAnnotation(AnnotationTypeMapping mapping, ClassLoader classLoader,
                                 Object source, Object rootAttributes, ValueExtractor valueExtractor,
                                 int aggregateIndex) {

        this(mapping, classLoader, source, rootAttributes, valueExtractor, aggregateIndex, null);
    }

    private TypeMappedAnnotation(AnnotationTypeMapping mapping, ClassLoader classLoader,
                                 Object source, Object rootAttributes, ValueExtractor valueExtractor,
                                 int aggregateIndex, int[] resolvedRootMirrors) {

        this.mapping = mapping;
        this.classLoader = classLoader;
        this.source = source;
        this.rootAttributes = rootAttributes;
        this.valueExtractor = valueExtractor;
        this.aggregateIndex = aggregateIndex;
        this.useMergedValues = true;
        this.attributeFilter = null;
        this.resolvedRootMirrors = (resolvedRootMirrors != null ? resolvedRootMirrors :
                mapping.getRoot().getMirrorSets().resolve(source, rootAttributes, this.valueExtractor));
        this.resolvedMirrors = (getDistance() == 0 ? this.resolvedRootMirrors :
                mapping.getMirrorSets().resolve(source, this, this::getValueForMirrorResolution));
    }

    private TypeMappedAnnotation(AnnotationTypeMapping mapping, ClassLoader classLoader,
                                 Object source, Object rootAnnotation, ValueExtractor valueExtractor,
                                 int aggregateIndex, boolean useMergedValues, Predicate<String> attributeFilter,
                                 int[] resolvedRootMirrors, int[] resolvedMirrors) {

        this.classLoader = classLoader;
        this.source = source;
        this.rootAttributes = rootAnnotation;
        this.valueExtractor = valueExtractor;
        this.mapping = mapping;
        this.aggregateIndex = aggregateIndex;
        this.useMergedValues = useMergedValues;
        this.attributeFilter = attributeFilter;
        this.resolvedRootMirrors = resolvedRootMirrors;
        this.resolvedMirrors = resolvedMirrors;
    }

    static <A extends Annotation> MergedAnnotation<A> from(Object source, A annotation) {
        AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(annotation.annotationType());
        return new TypeMappedAnnotation<>(
                mappings.get(0), null, source, annotation, AnnotationUtils::invokeAnnotationMethod, 0);
    }

    static <A extends Annotation> MergedAnnotation<A> of(
            ClassLoader classLoader, Object source,
            Class<A> annotationType, Map<String, ?> attributes) {

        AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(annotationType);
        return new TypeMappedAnnotation<>(
                mappings.get(0), classLoader, source, attributes, TypeMappedAnnotation::extractFromMap, 0);
    }


    static <A extends Annotation> TypeMappedAnnotation<A> createIfPossible(
            AnnotationTypeMapping mapping, MergedAnnotation<?> annotation) {

        if (annotation instanceof TypeMappedAnnotation) {
            TypeMappedAnnotation<?> typeMappedAnnotation = (TypeMappedAnnotation<?>) annotation;
            return createIfPossible(mapping, typeMappedAnnotation.source,
                    typeMappedAnnotation.rootAttributes,
                    typeMappedAnnotation.valueExtractor,
                    typeMappedAnnotation.aggregateIndex);
        }
        return createIfPossible(mapping, annotation.getSource(), annotation.synthesize(),
                annotation.getAggregateIndex());
    }


    static <A extends Annotation> TypeMappedAnnotation<A> createIfPossible(
            AnnotationTypeMapping mapping, Object source, Annotation annotation,
            int aggregateIndex) {

        return createIfPossible(mapping, source, annotation,
                AnnotationUtils::invokeAnnotationMethod, aggregateIndex);
    }


    private static <A extends Annotation> TypeMappedAnnotation<A> createIfPossible(
            AnnotationTypeMapping mapping, Object source, Object rootAttribute,
            ValueExtractor valueExtractor, int aggregateIndex) {

        try {
            return new TypeMappedAnnotation<>(mapping, null, source, rootAttribute,
                    valueExtractor, aggregateIndex);
        } catch (Exception ex) {
            AnnotationUtils.rethrowAnnotationConfigurationException(ex);
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    static Object extractFromMap(Method attribute, Object map) {
        return (map != null ? ((Map<String, ?>) map).get(attribute.getName()) : null);
    }

    @Override
    @SuppressWarnings("unchecked")
    public Class<A> getType() {
        return (Class<A>) this.mapping.getAnnotationType();
    }

    @Override
    public List<Class<? extends Annotation>> getMetaTypes() {
        return this.mapping.getMetaTypes();
    }

    @Override
    public boolean isPresent() {
        return true;
    }

    @Override
    public int getDistance() {
        return this.mapping.getDistance();
    }

    @Override
    public int getAggregateIndex() {
        return this.aggregateIndex;
    }

    @Override

    public Object getSource() {
        return this.source;
    }

    @Override
    public MergedAnnotation<?> getMetaSource() {
        AnnotationTypeMapping metaSourceMapping = this.mapping.getSource();
        if (metaSourceMapping == null) {
            return null;
        }
        return new TypeMappedAnnotation<>(metaSourceMapping, this.classLoader, this.source,
                this.rootAttributes, this.valueExtractor, this.aggregateIndex, this.resolvedRootMirrors);
    }

    @Override
    public MergedAnnotation<?> getRoot() {
        if (getDistance() == 0) {
            return this;
        }
        AnnotationTypeMapping rootMapping = this.mapping.getRoot();
        return new TypeMappedAnnotation<>(rootMapping, this.classLoader, this.source,
                this.rootAttributes, this.valueExtractor, this.aggregateIndex, this.resolvedRootMirrors);
    }

    @Override
    public boolean hasDefaultValue(String attributeName) {
        int attributeIndex = getAttributeIndex(attributeName, true);
        Object value = getValue(attributeIndex, true, false);
        return (value == null || this.mapping.isEquivalentToDefaultValue(attributeIndex, value, this.valueExtractor));
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T extends Annotation> MergedAnnotation<T> getAnnotation(String attributeName, Class<T> type)
            throws NoSuchElementException {

        int attributeIndex = getAttributeIndex(attributeName, true);
        Method attribute = this.mapping.getAttributes().get(attributeIndex);
        return (MergedAnnotation<T>) getRequiredValue(attributeIndex, attributeName);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T extends Annotation> MergedAnnotation<T>[] getAnnotationArray(
            String attributeName, Class<T> type) throws NoSuchElementException {

        int attributeIndex = getAttributeIndex(attributeName, true);
        Method attribute = this.mapping.getAttributes().get(attributeIndex);
        Class<?> componentType = attribute.getReturnType().getComponentType();
        return (MergedAnnotation<T>[]) getRequiredValue(attributeIndex, attributeName);
    }

    @Override
    public <T> Optional<T> getDefaultValue(String attributeName, Class<T> type) {
        int attributeIndex = getAttributeIndex(attributeName, false);
        if (attributeIndex == -1) {
            return Optional.empty();
        }
        Method attribute = this.mapping.getAttributes().get(attributeIndex);
        return Optional.ofNullable(adapt(attribute, attribute.getDefaultValue(), type));
    }

    @Override
    public MergedAnnotation<A> filterAttributes(Predicate<String> predicate) {
        if (this.attributeFilter != null) {
            predicate = this.attributeFilter.and(predicate);
        }
        return new TypeMappedAnnotation<>(this.mapping, this.classLoader, this.source, this.rootAttributes,
                this.valueExtractor, this.aggregateIndex, this.useMergedValues, predicate,
                this.resolvedRootMirrors, this.resolvedMirrors);
    }

    @Override
    public MergedAnnotation<A> withNonMergedAttributes() {
        return new TypeMappedAnnotation<>(this.mapping, this.classLoader, this.source, this.rootAttributes,
                this.valueExtractor, this.aggregateIndex, false, this.attributeFilter,
                this.resolvedRootMirrors, this.resolvedMirrors);
    }

    @Override
    public Map<String, Object> asMap(Adapt... adaptations) {
        return Collections.unmodifiableMap(asMap(mergedAnnotation -> new LinkedHashMap<>(), adaptations));
    }

    @Override
    public <T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T> factory, Adapt... adaptations) {
        T map = factory.apply(this);
        AttributeMethods attributes = this.mapping.getAttributes();
        for (int i = 0; i < attributes.size(); i++) {
            Method attribute = attributes.get(i);
            Object value = (isFiltered(attribute.getName()) ? null :
                    getValue(i, getTypeForMapOptions(attribute, adaptations)));
            if (value != null) {
                map.put(attribute.getName(),
                        adaptValueForMapOptions(attribute, value, map.getClass(), factory, adaptations));
            }
        }
        return map;
    }

    private Class<?> getTypeForMapOptions(Method attribute, Adapt[] adaptations) {
        Class<?> attributeType = attribute.getReturnType();
        Class<?> componentType = (attributeType.isArray() ? attributeType.getComponentType() : attributeType);
        if (Adapt.CLASS_TO_STRING.isIn(adaptations) && componentType == Class.class) {
            return (attributeType.isArray() ? String[].class : String.class);
        }
        return Object.class;
    }

    private <T extends Map<String, Object>> Object adaptValueForMapOptions(Method attribute, Object value,
                                                                           Class<?> mapType, Function<MergedAnnotation<?>, T> factory, Adapt[] adaptations) {
        if (value instanceof MergedAnnotation) {
            MergedAnnotation<?> annotation = (MergedAnnotation<?>) value;
            return (Adapt.ANNOTATION_TO_MAP.isIn(adaptations) ?
                    annotation.asMap(factory, adaptations) : annotation.synthesize());
        }
        if (value instanceof MergedAnnotation[]) {
            MergedAnnotation<?>[] annotations = (MergedAnnotation<?>[]) value;
            if (Adapt.ANNOTATION_TO_MAP.isIn(adaptations)) {
                Object result = Array.newInstance(mapType, annotations.length);
                for (int i = 0; i < annotations.length; i++) {
                    Array.set(result, i, annotations[i].asMap(factory, adaptations));
                }
                return result;
            }
            Object result = Array.newInstance(
                    attribute.getReturnType().getComponentType(), annotations.length);
            for (int i = 0; i < annotations.length; i++) {
                Array.set(result, i, annotations[i].synthesize());
            }
            return result;
        }
        return value;
    }

    @Override
    @SuppressWarnings("unchecked")
    protected A createSynthesizedAnnotation() {
        /**
         * 目标注解是root且root节点非合成注解
         */
        if (isTargetAnnotation(this.rootAttributes) && !isSynthesizable((Annotation) this.rootAttributes)) {
            return (A) this.rootAttributes;
        } else if (isTargetAnnotation(this.mapping.getAnnotation()) && !isSynthesizable(this.mapping.getAnnotation())) {
            return (A) this.mapping.getAnnotation();
        }
        return SynthesizedMergedAnnotationInvocationHandler.createProxy(this, getType());
    }

    private boolean isTargetAnnotation(Object obj) {
        return getType().isInstance(obj);
    }


    @Override
    protected <T> T getAttributeValue(String attributeName, Class<T> type) {
        int attributeIndex = getAttributeIndex(attributeName, false);
        return (attributeIndex != -1 ? getValue(attributeIndex, type) : null);
    }

    private int getAttributeIndex(String attributeName, boolean required) {
        int attributeIndex = (isFiltered(attributeName) ? -1 : this.mapping.getAttributes().indexOf(attributeName));
        if (attributeIndex == -1 && required) {
            throw new NoSuchElementException("No attribute named '" + attributeName +
                    "' present in merged annotation " + getType().getName());
        }
        return attributeIndex;
    }

    private Object getRequiredValue(int attributeIndex, String attributeName) {
        Object value = getValue(attributeIndex, Object.class);
        if (value == null) {
            throw new NoSuchElementException("No element at attribute index "
                    + attributeIndex + " for name " + attributeName);
        }
        return value;
    }

    private boolean isSynthesizable(Annotation annotation) {
        if (annotation instanceof SynthesizedAnnotation) {
            return false;
        }
        if (getDistance() > 0 && this.resolvedMirrors.length > 0) {
            return true;
        }
        return this.mapping.isSynthesizable();
    }

    private <T> T getValue(int attributeIndex, Class<T> type) {
        Method attribute = this.mapping.getAttributes().get(attributeIndex);
        Object value = getValue(attributeIndex, true, false);
        if (value == null) {
            value = attribute.getDefaultValue();
        }
        return adapt(attribute, value, type);
    }


    private Object getValue(int attributeIndex, boolean useConventionMapping, boolean forMirrorResolution) {
        AnnotationTypeMapping mapping = this.mapping;
        if (this.useMergedValues) {
            int mappedIndex = this.mapping.getAliasMapping(attributeIndex);
            if (mappedIndex == -1 && useConventionMapping) {
                mappedIndex = this.mapping.getConventionMapping(attributeIndex);
            }
            if (mappedIndex != -1) {
                mapping = mapping.getRoot();
                attributeIndex = mappedIndex;
            }
        }
        if (!forMirrorResolution) {
            attributeIndex =
                    (mapping.getDistance() != 0 ? this.resolvedMirrors : this.resolvedRootMirrors)[attributeIndex];
        }
        if (attributeIndex == -1) {
            return null;
        }
        if (mapping.getDistance() == 0) {
            Method attribute = mapping.getAttributes().get(attributeIndex);
            Object result = this.valueExtractor.extract(attribute, this.rootAttributes);
            return (result != null ? result : attribute.getDefaultValue());
        }
        return getValueFromMetaAnnotation(attributeIndex, forMirrorResolution);
    }


    private Object getValueFromMetaAnnotation(int attributeIndex, boolean forMirrorResolution) {
        Object value = null;
        if (this.useMergedValues || forMirrorResolution) {
            value = this.mapping.getMappedAnnotationValue(attributeIndex, forMirrorResolution);
        }
        if (value == null) {
            Method attribute = this.mapping.getAttributes().get(attributeIndex);
            value = AnnotationUtils.invokeAnnotationMethod(attribute, this.mapping.getAnnotation());
        }
        return value;
    }


    private Object getValueForMirrorResolution(Method attribute, Object annotation) {
        int attributeIndex = this.mapping.getAttributes().indexOf(attribute);
        boolean valueAttribute = VALUE.equals(attribute.getName());
        return getValue(attributeIndex, !valueAttribute, true);
    }

    @SuppressWarnings("unchecked")
    private <T> T adapt(Method attribute, Object value, Class<T> type) {
        if (value == null) {
            return null;
        }
        value = adaptForAttribute(attribute, value);
        type = getAdaptType(attribute, type);
        if (value instanceof Class && type == String.class) {
            value = ((Class<?>) value).getName();
        } else if (value instanceof String && type == Class.class) {
            value = ClassUtils.resolveClassName((String) value, getClassLoader());
        } else if (value instanceof Class[] && type == String[].class) {
            Class<?>[] classes = (Class<?>[]) value;
            String[] names = new String[classes.length];
            for (int i = 0; i < classes.length; i++) {
                names[i] = classes[i].getName();
            }
            value = names;
        } else if (value instanceof String[] && type == Class[].class) {
            String[] names = (String[]) value;
            Class<?>[] classes = new Class<?>[names.length];
            for (int i = 0; i < names.length; i++) {
                classes[i] = ClassUtils.resolveClassName(names[i], getClassLoader());
            }
            value = classes;
        } else if (value instanceof MergedAnnotation && type.isAnnotation()) {
            MergedAnnotation<?> annotation = (MergedAnnotation<?>) value;
            value = annotation.synthesize();
        } else if (value instanceof MergedAnnotation[] && type.isArray() && type.getComponentType().isAnnotation()) {
            MergedAnnotation<?>[] annotations = (MergedAnnotation<?>[]) value;
            Object array = Array.newInstance(type.getComponentType(), annotations.length);
            for (int i = 0; i < annotations.length; i++) {
                Array.set(array, i, annotations[i].synthesize());
            }
            value = array;
        }
        if (!type.isInstance(value)) {
            throw new IllegalArgumentException("Unable to adapt value of type " +
                    value.getClass().getName() + " to " + type.getName());
        }
        return (T) value;
    }

    @SuppressWarnings("unchecked")
    private Object adaptForAttribute(Method attribute, Object value) {
        Class<?> attributeType = ClassUtils.resolvePrimitiveIfNecessary(attribute.getReturnType());
        if (attributeType.isArray() && !value.getClass().isArray()) {
            Object array = Array.newInstance(value.getClass(), 1);
            Array.set(array, 0, value);
            return adaptForAttribute(attribute, array);
        }
        if (attributeType.isAnnotation()) {
            return adaptToMergedAnnotation(value, (Class<? extends Annotation>) attributeType);
        }
        if (attributeType.isArray() && attributeType.getComponentType().isAnnotation()) {
            MergedAnnotation<?>[] result = new MergedAnnotation<?>[Array.getLength(value)];
            for (int i = 0; i < result.length; i++) {
                result[i] = adaptToMergedAnnotation(Array.get(value, i),
                        (Class<? extends Annotation>) attributeType.getComponentType());
            }
            return result;
        }
        if ((attributeType == Class.class && value instanceof String) ||
                (attributeType == Class[].class && value instanceof String[]) ||
                (attributeType == String.class && value instanceof Class) ||
                (attributeType == String[].class && value instanceof Class[])) {
            return value;
        }
        if (attributeType.isArray() && isEmptyObjectArray(value)) {
            return emptyArray(attributeType.getComponentType());
        }
        if (!attributeType.isInstance(value)) {
            throw new IllegalStateException("Attribute '" + attribute.getName() +
                    "' in annotation " + getType().getName() + " should be compatible with " +
                    attributeType.getName() + " but a " + value.getClass().getName() +
                    " value was returned");
        }
        return value;
    }

    private boolean isEmptyObjectArray(Object value) {
        return (value instanceof Object[] && ((Object[]) value).length == 0);
    }

    private Object emptyArray(Class<?> componentType) {
        Object result = EMPTY_ARRAYS.get(componentType);
        if (result == null) {
            result = Array.newInstance(componentType, 0);
        }
        return result;
    }

    private MergedAnnotation<?> adaptToMergedAnnotation(Object value, Class<? extends Annotation> annotationType) {
        if (value instanceof MergedAnnotation) {
            return (MergedAnnotation<?>) value;
        }
        AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(annotationType).get(0);
        return new TypeMappedAnnotation<>(
                mapping, null, this.source, value, getValueExtractor(value), this.aggregateIndex);
    }

    private ValueExtractor getValueExtractor(Object value) {
        if (value instanceof Annotation) {
            return AnnotationUtils::invokeAnnotationMethod;
        }
        if (value instanceof Map) {
            return TypeMappedAnnotation::extractFromMap;
        }
        return this.valueExtractor;
    }

    @SuppressWarnings("unchecked")
    private <T> Class<T> getAdaptType(Method attribute, Class<T> type) {
        if (type != Object.class) {
            return type;
        }
        Class<?> attributeType = attribute.getReturnType();
        if (attributeType.isAnnotation()) {
            return (Class<T>) MergedAnnotation.class;
        }
        if (attributeType.isArray() && attributeType.getComponentType().isAnnotation()) {
            return (Class<T>) MergedAnnotation[].class;
        }
        return (Class<T>) ClassUtils.resolvePrimitiveIfNecessary(attributeType);
    }


    private boolean isFiltered(String attributeName) {
        if (this.attributeFilter != null) {
            return !this.attributeFilter.test(attributeName);
        }
        return false;
    }


    private ClassLoader getClassLoader() {
        if (this.classLoader != null) {
            return this.classLoader;
        }
        if (this.source != null) {
            if (this.source instanceof Class) {
                return ((Class<?>) this.source).getClassLoader();
            }
            if (this.source instanceof Member) {
                ((Member) this.source).getDeclaringClass().getClassLoader();
            }
        }
        return null;
    }

}
